/**
 *  \file main.c
 *
 *  \brief	The incredible fulgurator.
 *
 *  \author  Michael J. Hohmann <michael.hohmann@linde-mh.de>
 *
 *
 */

/*===============================================[ private includes  ]================================================*/
//#define MAIN_LINKAGE
#include "main.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include "../ihex/ihex.h"
#include "../sections/sections.h"
#include "../cant/cant.h"
#include "../kwptp/kwptp.h"
#include "../kwp/kwp_sid.h"
#include "../secacc/secacc_crypto.h"
#include "../parameter/parameter.h"
#include "../segfile/segfile.h"

/*===============================================[ private defines   ]================================================*/
#define FULGURATOR_VERSION "1.5.2"

/*===============================================[ private datatypes ]================================================*/

/*===============================================[ private variables ]================================================*/
/* object for CAN API */
static struct cant_obj *mycan;

/* objects for KWP transport protocol API */
static struct kwptp mykwptp;
static struct kwptp mykwptp_hwid;
static struct kwptp mykwptp_emergency;

/* objects for KWP messages */
static struct kwptp_msg normal_msg;
static struct kwptp_msg hwid_msg;
static struct kwptp_msg emergency_msg;
static U32 default_timeout = 100;                  /* KWP message timeout in units of 10 milliseconds */

/* temporary FILE pointer */
static FILE *fh;

/* segmentation file object */
static segfile_t* segfile;

/* pointers to sections and section lists */
static struct section* downloadSection;
static struct section* deleteSection;
static struct section_list *sections_to_download;
static struct section_list *sections_to_delete;

/* variables to control the main Fulgurator state machine */
static volatile BO main_running;                   /* main loop still running? */
static U32 emergency_message_countdown;            /* how many init01 should still be sent */
static U32 emergency_message_timeout = 2;          /* time between subsequent init01 messages (unit is 10 ms)*/
static BO address_size_switched;                   /* has the address-size been changed between 24/32-bit? */

/* temporary variables used to control the actual data transfer */
static U32 address;                                /* start address of currently transmitted data segment */
static U32 size;                                   /* size of currently transmitted data segment */
static U8 *ptransmit;                              /* pointer of segment that is transmitted next */
static U16 maxNumberOfBytes;                       /* amount of bytes per single data transfer */

/*===============================================[ public variables  ]================================================*/

/*===============================================[ private functions ]================================================*/
/* callback functions for main Fulgurator state machine */
static void fulgurator(void);
static RC fulgurator_state_try_emergency_entry(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_read_hwid(void *obj, struct kwptp_msg* msg);
static RC fulgurator_state_start_diagnostic_session(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_security_access_34_request_seed(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_security_access_34_send_key(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_enter_passiv_mode(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_start_flasher(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_security_access_12_request_seed(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_security_access_12_send_key(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_start_programming_session(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_request_download(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_delete_flash(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_transfer_data(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_request_transfer_exit(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_calculate_checksum(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_reset(void *obj, struct kwptp_msg *msg);
static RC fulgurator_state_after_reset(void *obj, struct kwptp_msg* msg);
static U16 fulgurator_checksum(const void* data, const U32 size);
/*====================================================================================================================*/

static RC can_transmit_function(void *obj, struct kwptp_can_msg *can_msg) {
   struct cant_obj *mycan = obj;
   can_msg->id = parameter.kwp_tx_id;
   cant_tx(mycan, can_msg);
   return RC_SUCCESS;
}

static RC can_transmit_emergency_function(void *obj, struct kwptp_can_msg *can_msg) {
   struct cant_obj *mycan = obj;
   can_msg->id = parameter.init_01_tx_id;
   cant_tx(mycan, can_msg);
   return RC_SUCCESS;
}
static RC can_transmit_hwid_function(void *obj, struct kwptp_can_msg *can_msg) {
   struct cant_obj *mycan = obj;
   can_msg->id = parameter.hwid_tx_id;
   cant_tx(mycan, can_msg);
   return RC_SUCCESS;
}

void* cant_rx_callback(struct cant_obj * me, struct cant_msg* msg, void** data) {
   kwptp_process(&mykwptp, msg);
   return cant_rx_callback;
}

void* cant_init01_response_callback(struct cant_obj * me, struct cant_msg* msg, void** data) {
   kwptp_process(&mykwptp_emergency, msg);
   return cant_init01_response_callback; /* dont call this function anymore */
}

void* cant_hwid_response_callback(struct cant_obj * me, struct cant_msg* msg, void** data) {
   kwptp_process(&mykwptp_hwid, msg);
   return cant_hwid_response_callback;
}

static void exit_with_waiting(int returnCode) {
   if (parameter.wait_before_exit) {
      printf("\n\tHit any key to continue...\n\n");
      while (!kbhit()) {
         Sleep(100);
      };
   }
   exit(returnCode);
}

/**
 *  Initialization.
 *
 *  Initialize the main module.
 *
 *  \return RC_SUCCESS if successful.
 */
int main(int argc, char *argv[]) {
   CH *name;

   setbuf(stdout, NULL);

   printf("\n+-----------------------------------------------------------------------------+\n");
   printf("| Fulgurator v" FULGURATOR_VERSION "                                                           |\n");
   printf("| Copyright 2014 Linde Material Handling. All rights reserved                 |\n");
   printf("+-----------------------------------------------------------------------------+\n\n");

   if (RC_SUCCESS != parameter_initialize(argc, (void*) argv)) {
      /* this will always exit immediately, because the parameter to wait before exiting is defined in the .ini */
      exit_with_waiting(EXIT_FAILURE);
   }

   printf("\nConnecting to Vector CAN-interface\n");

   if (NULL == (mycan = cant_create(parameter.can_baud, "Fulgurator"))) {
      printf("\nError:  Can't open CAN-interface!\n");
      printf("        Please check your wiring or the Vector Hardware configuration.\n");
      exit_with_waiting(EXIT_FAILURE);
   }

   printf("\tBaud Rate: %u KBaud\n", (unsigned int) parameter.can_baud / 1000);
   printf("\tSerial Nr: %u\n", (unsigned int) mycan->serial_number);

   if (NULL == (name = parameter_test_serial_number(mycan->serial_number))) {
      exit_with_waiting(EXIT_FAILURE);
   }

   cant_activate(mycan, TRUE);

   if (parameter.use_manual_separation_time) {
      printf("\nUsing manual CAN frame separation time of %x ms.\n", (unsigned int) parameter.manual_separation_time);
   }

   /* initialize send and receive functions for normal KWP messages */
   cant_install_rx_callback(mycan, parameter.kwp_rx_id, cant_rx_callback, NULL );
   kwptp_initialize(&mykwptp, can_transmit_function, mycan);

   /* initialize send and receive functions for init01 KWP messages */
   cant_install_rx_callback(mycan, parameter.init_01_rx_id, cant_init01_response_callback, NULL );
   kwptp_initialize(&mykwptp_emergency, can_transmit_emergency_function, mycan);

   /* initialize send and receive functions for hardware ID request messages */
   if (parameter.kwp_rx_id != parameter.hwid_rx_id && parameter.hwid_rx_id != 0) {
      cant_install_rx_callback(mycan, parameter.hwid_rx_id, cant_hwid_response_callback, NULL );
   }
   if (parameter.kwp_tx_id != parameter.hwid_tx_id && parameter.hwid_tx_id != 0) {
      kwptp_initialize(&mykwptp_hwid, can_transmit_hwid_function, mycan);
   }

   if (parameter.address_size != 24 && parameter.address_size != 32) {
      printf("\nError: Only 24-bit or 32-bit addresses are supported, you requested %u.\n",
            (unsigned int) parameter.address_size);
      exit_with_waiting(EXIT_FAILURE);
   }

   /* override STmin for normal KWP messages if desired */
   mykwptp.use_manual_st_min = parameter.use_manual_separation_time; /* FALSE if not present in the .ini file */
   mykwptp.manual_st_min = parameter.manual_separation_time; /* 0 if not present in the .ini file */

   /* load .hex-file and generate a list of memory sections that are contained in it */
   printf("\nLoading .hex-file: %s\n", parameter.hex_file_name);
   if (NULL == (fh = fopen(parameter.hex_file_name, "r"))) {
      printf("\tError: .hex-file could not be opened.\n");
      exit_with_waiting(EXIT_FAILURE);
   }

   sections_to_download = ihex_load(fh);
   sections_to_delete = NULL;

   /* load segmentation file */
   if (parameter.segfile_valid && parameter.segfile[0] != '\0') {

      /* try to read segmentation file */
      printf("\nLoading segmentation file: %s\n", parameter.segfile);
      FILE* fs = fopen(parameter.segfile, "r");

      if (NULL == fs) {
         printf("\tSegmentation file could not be found.\n");
         exit_with_waiting(EXIT_FAILURE);
      }
      fclose(fs);

      if (NULL == (segfile = segfile_open(parameter.segfile))) {
         printf("\tCould not parse segmentation file.\n");
         exit_with_waiting(EXIT_FAILURE);
      }

      sections_to_delete = segfile_get_sections(segfile);
      sections_fill(sections_to_delete, parameter.fillbyte);
      sections_enforce_bounds(sections_to_download, sections_to_delete);
   }

   /* try to maximize the sections that will later be downloaded within the bounds of the segmentation file */
   if (RC_ERROR == sections_maximize(sections_to_download, sections_to_delete, parameter.fillbyte, parameter.fill_only_hexfile)) {
      printf("\tCould not merge sections from hex file into larger sections before downloading.\n");
      exit_with_waiting(EXIT_FAILURE);
   }

   if (NULL != sections_to_delete) {
      unsigned int downloadCount;
      unsigned int deleteCount;
      LIST_COUNT(sections_to_download, &downloadSection, &downloadCount);
      LIST_COUNT(sections_to_delete, &deleteSection, &deleteCount);

      if (downloadCount != deleteCount) {
         printf("\tError: Number of sections for download does not match number of sections to delete.\n");
         exit_with_waiting(EXIT_FAILURE);
      }
   }

   /* run the main Fulgurator state machine */
   fulgurator();

   exit_with_waiting(EXIT_SUCCESS);
   return EXIT_SUCCESS; /* this is just to make the compiler happy */
}

static void fulgurator(void) {
   emergency_message_countdown = parameter.emergency_duration / (emergency_message_timeout * 10);

   if (parameter.emergency_start) {
      printf("\nTry to enter bootblock by init_01 message.\n");
      printf("\tPlease restart the ECU within the next %.1f seconds.", emergency_message_countdown * emergency_message_timeout / 100.0f);
      emergency_msg.len = 3;
      emergency_msg.timeout = emergency_message_timeout;
      emergency_msg.callback = fulgurator_state_try_emergency_entry;
      emergency_msg.data[0] = kwp_sid_start_routine_by_local_identifier;
      emergency_msg.data[1] = 0x80;
      emergency_msg.data[2] = 0xFB;
      kwptp_transmit(&mykwptp_emergency, &emergency_msg);
   } else { /* try to read software Module */
      printf("\nRead SoftwareModule\n");
      normal_msg.len = 2;
      normal_msg.timeout = default_timeout;
      normal_msg.callback = fulgurator_state_read_hwid;
      normal_msg.data[0] = kwp_sid_read_ecu_identification;
      normal_msg.data[1] = 0x8A;
      kwptp_transmit(&mykwptp, &normal_msg);
   }

   main_running = TRUE;

   while (main_running) {
      Sleep(100);       /* milliseconds */
   }
}

static RC fulgurator_state_try_emergency_entry(void *obj, struct kwptp_msg *msg) {
   if (msg->state == kwptp_msg_state_timed_out) {
      if (emergency_message_countdown == 0) {
         printf("\n\tAll init_01-messages timed out.\n");
         main_running = FALSE;
      } else {
         emergency_message_countdown--;
         if (emergency_message_countdown * emergency_message_timeout % 100 == 0) {
               printf("\r\tPlease restart the ECU within the next %.1f seconds.", emergency_message_countdown * emergency_message_timeout / 100.0f);
         }
         msg->len = 3;
         msg->timeout = emergency_message_timeout;
         msg->callback = fulgurator_state_try_emergency_entry;
         msg->data[0] = kwp_sid_start_routine_by_local_identifier;
         msg->data[1] = 0x80;
         msg->data[2] = 0xFB;
         kwptp_transmit(&mykwptp_emergency, msg);
      }
   } else if (msg->state == kwptp_msg_state_positive_response || msg->state == kwptp_msg_state_negative_response) {
      if (msg->state == kwptp_msg_state_positive_response) {
         printf("\n\tPositive response\n");
      } else if (msg->state == kwptp_msg_state_negative_response) {
         printf("\n\tNegative response - Try to continue anyway\n");
      }

      /* Wait until the bus calms down and then flush all receive and transmit queues of the CAN-interface to make sure
       * there will be no messages left for processing. It is not exactly clear whether this function really does what
       * its name suggests, but it seems to help */
      Sleep(1000);
      cant_flushQueues(mycan);

      printf("\nRead SoftwareModule\n");
      normal_msg.len = 2;
      normal_msg.timeout = default_timeout;
      normal_msg.callback = fulgurator_state_read_hwid;
      normal_msg.data[0] = kwp_sid_read_ecu_identification;
      normal_msg.data[1] = 0x8A;
      kwptp_transmit(&mykwptp, &normal_msg);
   } else {
      main_running = FALSE;
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_read_hwid(void *obj, struct kwptp_msg* msg) {
   if (msg->state == kwptp_msg_state_timed_out) {

      printf("\tTimeout\n");

      if (parameter.automatic_detection && !parameter.emergency_start) {
         printf("\tTrying to enter bootblock.\n");
         printf("\tPlease restart the ECU within the next %.1f seconds.", emergency_message_countdown * emergency_message_timeout / 100.0f);
         emergency_msg.len = 3;
         emergency_msg.timeout = emergency_message_timeout;
         emergency_msg.callback = fulgurator_state_try_emergency_entry;
         emergency_msg.data[0] = kwp_sid_start_routine_by_local_identifier;
         emergency_msg.data[1] = 0x80;
         emergency_msg.data[2] = 0xFB;
         kwptp_transmit(&mykwptp_emergency, &emergency_msg);
      } else {
         main_running = FALSE;
      }
   } else if (msg->state == kwptp_msg_state_positive_response || msg->state == kwptp_msg_state_negative_response) {

      if (msg->state == kwptp_msg_state_positive_response) {
         if (msg->data[1] == 0) {
            printf("\tSoftwareModule is 0, i.e. responses come from the bootblock.\n");
         } else if (msg->data[1] == 1) {
            printf("\tSoftwareModule is 1, i.e. responses come from the truck software.\n");
         } else {
            printf("\tSoftwareModule has unknown value %u.\n", msg->data[1]);
         }
      } else if (msg->state == kwptp_msg_state_negative_response) {
         printf("\tNegative response - It seems the ECU doesn't support the SoftwareModule parameter.\n");
      }

      /*  read hardware ID */
      if (parameter.check_hwid_valid && parameter.check_hwid[0] != '\0') {
         printf("\nRead Hardware ID (Hardware ID check is on)\n");
      } else {
         printf("\nRead Hardware ID (Hardware ID check is off)\n");
      }

      /* decide which message object to use (the normal one or the special one for hardware ID requests) */
      if (parameter.kwp_tx_id != parameter.hwid_tx_id && parameter.hwid_tx_id != 0) {
         hwid_msg.len = 2;
         hwid_msg.timeout = default_timeout;
         hwid_msg.callback = fulgurator_state_start_diagnostic_session;
         hwid_msg.data[0] = kwp_sid_read_ecu_identification;
         hwid_msg.data[1] = 0x80;
         kwptp_transmit(&mykwptp_hwid, &hwid_msg);
      } else {
         msg->len = 2;
         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_start_diagnostic_session;
         msg->data[0] = kwp_sid_read_ecu_identification;
         msg->data[1] = 0x80;
         kwptp_transmit(&mykwptp, msg);
      }
   } else {
      main_running = FALSE;
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_start_diagnostic_session(void *obj, struct kwptp_msg *msg) {

   BO hwid_ok = FALSE;        /* assume hardware ID is wrong */

   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");

         /* continue anyway if no hardware ID check is requested */
         if (!parameter.check_hwid_valid || parameter.check_hwid[0] == '\0') {
            hwid_ok = TRUE;
         }
         break;
      }
      case kwptp_msg_state_positive_response: {

         /* buffer to store the HW ID as a 0-terminated string. HW ID has a max. length of 11 chars + 0-termination */
         char hwid_as_string[12];

         /* print Hardware ID */
         if (msg->len == 5) {
            /* Hardware ID is an U32. An U32 as string has a maximum length of 10 characters */
            sprintf(hwid_as_string, "%u", *((unsigned int*) (&(msg->data[1]))));
            printf("\tHardware ID is %s\n", hwid_as_string);
         } else if (msg->len == 11 || msg->len == 12) {
            /* Hardware ID is already a string, but probably not 0-terminated */
            sprintf(hwid_as_string, "%.*s", msg->len, &(msg->data[1]));
            printf("\tHardware ID is %s\n", hwid_as_string);
         } else {
            printf("\tError: Hardware ID has invalid format\n");
         }

         /* check Hardware ID if necessary */
         if (parameter.check_hwid_valid && parameter.check_hwid[0] != '\0') {
            if (0 == strcmp(parameter.check_hwid, hwid_as_string)) {
               hwid_ok = TRUE;
            } else {
               printf("\tError: Wrong Hardware ID.\n");
            }
         } else {
            hwid_ok = TRUE;
         }

         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative response\n");

         /* continue anyway if no hardware ID check is requested */
         if (!parameter.check_hwid_valid || parameter.check_hwid[0] == '\0') {
            hwid_ok = TRUE;
         }
         break;
      }
      default: {
         break;
      }
   }

   if (hwid_ok == TRUE) {
      /*  start diagnostic session */
      printf("\nStarting Diagnostic Session\n");
      normal_msg.len = 2;
      normal_msg.timeout = default_timeout;
      normal_msg.callback = fulgurator_state_security_access_34_request_seed;
      normal_msg.data[0] = kwp_sid_start_diagnostic_session;
      normal_msg.data[1] = 0x81;
      kwptp_transmit(&mykwptp, &normal_msg);
   } else {
      main_running = FALSE;
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_security_access_34_request_seed(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {

         printf("\nSecurity access 3 request seed\n");
         msg->len = 2;
         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_security_access_34_send_key;
         msg->data[0] = kwp_sid_security_access;
         msg->data[1] = 0x03;
         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }
   return RC_SUCCESS;
}

static RC fulgurator_state_security_access_34_send_key(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {
         U32 key;
         U32 seed;
         memcpy(&seed, &msg->data[2], 4);
         key = secacc_crypto_34(seed);
         printf("\tSeed: 0x%08X, Key: 0x%08X\n", (unsigned int) seed, (unsigned int) key);

         printf("\nSecurity access 4 send key\n");

         msg->len = 6;
         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_enter_passiv_mode;
         msg->data[0] = kwp_sid_security_access;
         msg->data[1] = 0x04;
         memcpy(&msg->data[2], &key, 4);
         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_enter_passiv_mode(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {
         printf("\nEnter Passive Mode\n");
         msg->len = 3;
         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_start_flasher;
         msg->data[0] = kwp_sid_start_routine_by_local_identifier;
         msg->data[1] = 0x04;
         msg->data[2] = 0x00;
         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_start_flasher(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {
         printf("\nStart Flasher Routine\n");
         msg->len = 3;
         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_security_access_12_request_seed;
         msg->data[0] = kwp_sid_start_routine_by_local_identifier;
         msg->data[1] = 0x01;
         msg->data[2] = 0x00;
         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }
   return RC_SUCCESS;
}

static RC fulgurator_state_security_access_12_request_seed(void *obj, struct kwptp_msg *msg) {

   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {
         Sleep(500);       /* wait until StartFlasherRoutine is completed */
         printf("\nSecurity access 1 request seed\n");
         msg->len = 2;
         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_security_access_12_send_key;
         msg->data[0] = kwp_sid_security_access;
         msg->data[1] = 0x01;
         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_security_access_12_send_key(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {
         U32 key;
         U32 seed;
         memcpy(&seed, &msg->data[2], 4);
         key = secacc_crypto_12(seed);
         printf("\tSeed: 0x%08X, Key: 0x%08X\n", (unsigned int) seed, (unsigned int) key);

         printf("\nSecurity access 2 send key\n");
         msg->len = 6;
         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_start_programming_session;
         msg->data[0] = kwp_sid_security_access;
         msg->data[1] = 0x02;
         memcpy(&msg->data[2], &key, 4);
         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_start_programming_session(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {
         printf("\nStart Programming Session\n");
         msg->len = 2;
         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_request_download;
         msg->data[0] = kwp_sid_start_diagnostic_session;
         msg->data[1] = 0x85;
         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_request_download(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {

         /* get next section */
         LIST_POP_FRONT(sections_to_download, &downloadSection);

         if (NULL != downloadSection) {

            /* there is another section to download */
            address = LIST_DATA(downloadSection)->address;
            size = LIST_DATA(downloadSection)->size;
            ptransmit = &LIST_DATA(downloadSection)->data[0];

            /* get corresponding deleteSection if flashing with segFile */
            if (NULL != sections_to_delete) {
               LIST_POP_FRONT(sections_to_delete, &deleteSection);
            } else {
               deleteSection = downloadSection;
            }

            printf("\nRequest Download (Start: 0x%0*X, Size: ", (int) (parameter.address_size >> 2), (int) address);
            printf("0x%0*X)\n", (int) (parameter.address_size >> 2), (int) size);

            msg->timeout = default_timeout;
            msg->callback = fulgurator_state_delete_flash;
            msg->data[0] = kwp_sid_request_download;

            if (parameter.address_size == 24) {
               msg->len = 8;
               memcpy(&msg->data[1], &address, 3);
               msg->data[4] = 0x00;
               memcpy(&msg->data[5], &size, 3);
            } else {
               msg->len = 10;
               memcpy(&msg->data[1], &address, 4);
               msg->data[5] = 0x00;
               memcpy(&msg->data[6], &size, 4);
            }
            kwptp_transmit(&mykwptp, msg);

         } else {

            /* all sections have been downloaded */
            printf("\nStop Programming and Diagnostic Session\n");
            msg->len = 1;
            msg->timeout = default_timeout;
            msg->data[0] = kwp_sid_stop_diagnostic_session;

            if (parameter.reset_after_programming) {
               msg->callback = fulgurator_state_reset;
            } else {
               msg->callback = fulgurator_state_after_reset;
            }
            kwptp_transmit(&mykwptp, msg);
         }

         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_delete_flash(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {

         /* positive response to 'RequestDownload' contains the amount of bytes that the ECU expects per transfer */
         if (msg->len == 2) {
            maxNumberOfBytes = msg->data[1];
         } else if (msg->len == 3) {
            maxNumberOfBytes = msg->data[2] * 256 + msg->data[1];
         }

         unsigned int deleteStart = LIST_DATA(deleteSection)->address;
         unsigned int deleteLength = LIST_DATA(deleteSection)->size;

         printf("\n\tDelete Flash (Start: ");
         printf("0x%0*X, Size: 0x%0*X)\n", (int) (parameter.address_size >> 2), deleteStart,
                           (int) (parameter.address_size >> 2), deleteLength);

         msg->timeout = default_timeout;
         if (size == 0) {
            /* empty sections occur if a segment is in the seg-file, but no corresponding data in the hex-file */
            msg->callback = fulgurator_state_request_download;
         } else {
            msg->callback = fulgurator_state_transfer_data;
         }
         msg->data[0] = kwp_sid_start_routine_by_local_identifier;
         msg->data[1] = 0x02;

         if (parameter.address_size == 24) {
            msg->len = 8;
            memcpy(&msg->data[2], &deleteStart, 3);
            memcpy(&msg->data[5], &deleteLength, 3);

         } else {
            msg->len = 10;
            memcpy(&msg->data[2], &deleteStart, 4);
            memcpy(&msg->data[6], &deleteLength, 4);
         }

         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");

         if ((parameter.automatic_detection) && (FALSE == address_size_switched)) {
            if (24 == parameter.address_size) {
               printf("\t24bit addresses don't work, try 32bit addresses.\n");
               parameter.address_size = 32;
            } else {
               printf("\t32bit addresses don't work, try 24bit addresses.\n");
               parameter.address_size = 24;
            }

            address_size_switched = TRUE;

            msg->timeout = default_timeout;
            msg->callback = fulgurator_state_delete_flash;
            msg->data[0] = kwp_sid_request_download;

            printf("\nRequest Download (Start: 0x%0*X, Size: ", (int) (parameter.address_size >> 2), (int) address);
            printf("0x%0*X)\n", (int) (parameter.address_size >> 2), (int) size);
            if (parameter.address_size == 24) {
               msg->len = 8;
               memcpy(&msg->data[1], &address, 3);
               msg->data[4] = 0x00;
               memcpy(&msg->data[5], &size, 3);
            } else {
               msg->len = 10;
               memcpy(&msg->data[1], &address, 4);
               msg->data[5] = 0x00;
               memcpy(&msg->data[6], &size, 4);
            }
            kwptp_transmit(&mykwptp, msg);
         } else {
            main_running = FALSE;
         }

         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }
   return RC_SUCCESS;
}

static RC fulgurator_state_transfer_data(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\n\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {

         /* print the following only once before the transfer starts */
         if (LIST_DATA(downloadSection)->size == size) {
            printf("\n\tTransfer Data (0x%0*X Byte remaining)", (int) (parameter.address_size >> 2),
                  (unsigned int) size);
         }

         /* transfer a maximum of 'maxNumberOfBytes' */
         U16 transferAmount = (size > maxNumberOfBytes) ? maxNumberOfBytes : size;

         msg->len = transferAmount + 1;
         msg->data[0] = kwp_sid_transfer_data;
         memcpy(&msg->data[1], ptransmit, transferAmount);
         size -= transferAmount;
         ptransmit += transferAmount;

         if (0 == size % 0x1000) {
            printf("\r\tTransfer Data (0x%0*X Byte remaining)", (int) (parameter.address_size >> 2),
                  (unsigned int) size);
         }

         /* determine what to do after the transfer */
         if (0 == size) {
            printf("\n");
            size = LIST_DATA(downloadSection)->size; /* VERY IMPORTANT: Reset Size to original value */
            msg->callback = fulgurator_state_request_transfer_exit; /* all data transferred */
         } else {
            msg->callback = fulgurator_state_transfer_data; /* transfer more data */
         }

         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\n\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_request_transfer_exit(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {
         printf("\n\tRequest Transfer Exit\n");

         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_calculate_checksum;
         msg->data[0] = kwp_sid_request_transfer_exit;

         if (parameter.address_size == 24) {
            msg->len = 8;
            memcpy(&msg->data[1], &address, 3);
            msg->data[4] = 0x00;
            memcpy(&msg->data[5], &size, 3);
         } else {
            msg->len = 10;
            memcpy(&msg->data[1], &address, 4);
            msg->data[5] = 0x00;
            memcpy(&msg->data[6], &size, 4);
         }
         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_calculate_checksum(void *obj, struct kwptp_msg *msg) {
   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {
         printf("\n\tVerifying checksum of download\n");
         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_request_download;
         msg->data[0] = kwp_sid_start_routine_by_local_identifier;
         msg->data[1] = 0x03;

         U32 endaddress = address + size - 1;
         U16 cs = fulgurator_checksum(LIST_DATA(downloadSection)->data, size);

         if (parameter.address_size == 24) {
            msg->len = 10;
            memcpy(&msg->data[2], &address, 3);
            memcpy(&msg->data[5], &endaddress, 3);
            memcpy(&msg->data[8], &cs, 2);
         } else {
            msg->len = 12;
            memcpy(&msg->data[2], &address, 4);
            memcpy(&msg->data[6], &endaddress, 4);
            memcpy(&msg->data[10], &cs, 2);
         }
         kwptp_transmit(&mykwptp, msg);
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_reset(void *obj, struct kwptp_msg *msg) {

   switch (msg->state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_positive_response: {
         printf("\nRequest ECU-Reset\n");
         msg->len = 2;
         msg->timeout = default_timeout;
         msg->callback = fulgurator_state_after_reset;
         msg->data[0] = kwp_sid_ecu_reset;
         msg->data[1] = 0x01;
         kwptp_transmit(&mykwptp, msg);
         main_running = FALSE;
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         main_running = FALSE;
         break;
      }
      default: {
         main_running = FALSE;
         break;
      }
   }

   return RC_SUCCESS;
}

static RC fulgurator_state_after_reset(void *obj, struct kwptp_msg* msg) {
   switch (normal_msg.state) {
      case kwptp_msg_state_timed_out: {
         printf("\tTimeout\n");
         break;
      }
      case kwptp_msg_state_positive_response: {
         printf("\nSoftware was successfully downloaded.\n");
         break;
      }
      case kwptp_msg_state_negative_response: {
         printf("\tNegative Response\n");
         break;
      }
      default:
         break;
   }

   return RC_SUCCESS;
}

static U16 fulgurator_checksum(const void* data, const U32 size) {
   U16 rv = 0;
   const U8* p = data;
   U32 internalSize = size;
   while (internalSize--) {
      rv += *p;
      p++;
   }
   return rv;
}

/***********************************************************************************************************************
 *                             Copyright 2010 Linde Material Handling. All rights reserved.                             *
 ***********************************************************************************************************************/
